package org.cerberus.platform.internal.impl;

import java.util.concurrent.ArrayBlockingQueue;

import org.cerberus.platform.internal.IConditional;
import org.cerberus.platform.internal.IEventQueue;

public class PlatformEventQueue implements IEventQueue {

	private boolean stopped;
	private boolean stopping;
	
	private ArrayBlockingQueue<Runnable> queue;
	private ArrayBlockingQueue<Runnable> priorityQueue;
	
	/**
	 * Stores the currently in progress Runnable. Is synchronized to EventQueue.
	 * 
	 * This member may be modified asynchronously, synchronize to queue before
	 * accessing it.
	 */
	private Runnable currentRunnable;
	
	private void init() {
		queue = new ArrayBlockingQueue<Runnable>(100);
		priorityQueue = new ArrayBlockingQueue<Runnable>(100);
	}

	@Override
	public synchronized void postEvent(Runnable run) {
		if (!stopping) {
			try {
				queue.put(run);
			} catch (InterruptedException e) {
				// event will get lost
			}
		}
	}
	
	public synchronized void postPriorityEvent(Runnable run) {
		if (!stopping) {
			if (currentRunnable == null) {
				throw new IllegalStateException("Priority events may only be posted while a event is processed.");
			}
			try {
				priorityQueue.put(run);
			} catch (InterruptedException e) {
				// event will get lost
			}
		}
	}

	@Override
	public void run() {
		init();
		eventLoop(new IConditional() {
			@Override
			public boolean evaluate() {
				return !stopped;
			}
		});
		destroy();
	}

	
	private void eventLoop(IConditional conditional) {
		while (conditional.evaluate()) {
			Runnable run = null;
			try {
				// poll priortyQueue first, does not need to wait
				// because priority events may only occur while processing
				// another one (same thread).
				run = priorityQueue.poll();
				if (run == null) {
					// wait here for new events to become available.
					// priorityQueue elements can only become available while
					// an element is being processed.
					run = queue.take();
				}
				
				// the current task needs to be synchronized
				// to the queue because postEvents needs to
				// access them.
				synchronized (this) {
					// postPriorityEvent needs to check if
					// an event is currently being processed.
					currentRunnable = run;
				}
				run.run();
			} catch (InterruptedException e) {
			} catch (Throwable ex) {
				ex.printStackTrace();
				//TODO error handling
			} finally {
				synchronized(this) {
					currentRunnable = null;
				}
			}
		}
	}
	
	private void destroy() {
		queue.clear();
		queue = null;
	}
	
	@Override
	public void push(IConditional conditional) {
		eventLoop(conditional);
	}

	@Override
	public synchronized void stop() {
		postEvent(new Runnable() {
			@Override
			public void run() {
				stopped = true;
			}
		});
		stopping = true;
	}

}
